package ru.pb.game.objects.storage;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Deque;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.apache.log4j.Logger;

/**
 * @author Felixx
 * @param <E>
 */
public class ObjectArray<E extends IStoreableObject> implements Iterable<E>
{
	private static final Logger _log = Logger.getLogger(ObjectArray.class);
	private final String _name;
	private final int _resizeStep;

	private final Deque<Integer> _freeIndexes;
	private E[] _elements;
	private int _size = 0;
	private int _realSize = 0;
	private int _initialCapacity;
	private final Lock _lock = new ReentrantLock();

	@SuppressWarnings("unchecked")
	public ObjectArray(StorageId t)
	{
		_name = t.getName();
		_resizeStep = t.getStep();
		_initialCapacity = t.getInitSize();

		if(_initialCapacity < 0)
		{
			throw new IllegalArgumentException("Illegal Capacity (" + _name + "): " + _initialCapacity);
		}

		if(_resizeStep < 1)
		{
			throw new IllegalArgumentException("Illegal resize step (" + _name + "): " + _resizeStep);
		}

		_freeIndexes = new ArrayDeque<Integer>(_resizeStep);
		_elements = (E[]) new IStoreableObject[_initialCapacity];
	}

	public int initialCapacity()
	{
		return _initialCapacity;
	}

	public int size()
	{
		return _size;
	}

	public int getRealSize()
	{
		return _realSize;
	}

	public int capacity()
	{
		return _elements.length;
	}

	public int add(E e)
	{
		_lock.lock();
		try
		{
			Integer freeIndex = _freeIndexes.pollLast();
			if(freeIndex != null)
			{
				_realSize += 1;
				_elements[freeIndex] = e;
				return freeIndex;
			}

			if(_elements.length <= _size)
			{
				int newCapacity = _elements.length + _resizeStep;
				_log.info("Object array [" + _name + "] resized: " + _elements.length + " -> " + newCapacity);
				_elements = Arrays.copyOf(_elements, newCapacity);
			}
			_elements[(_size++)] = e;
			_realSize += 1;

			return _size - 1;
		}
		finally
		{
			_lock.unlock();
		}
	}

	public E remove(int index, int expectedObjId)
	{
		_lock.lock();
		try
		{
			if(index >= _size)
			{
				return null;
			}

			E old = _elements[index];

			if(old == null || old.getObjectId() != expectedObjId)
			{
				return null;
			}
			_elements[index] = null;
			_realSize -= 1;

			if(index == _size - 1)
			{
				_size -= 1;
			}
			else
			{
				_freeIndexes.add(index);
			}

			return old;
		}
		finally
		{
			_lock.unlock();
		}
	}

	public E get(int index)
	{
		return (index >= _size) ? null : _elements[index];
	}

	public E findByObjectId(int objId)
	{
		if(objId <= 0)
		{
			return null;
		}
		for(int i = 0; i < _size; ++i)
		{
			E o = _elements[i];
			if(o != null && o.getObjectId() == objId)
			{
				return o;
			}
		}
		return null;
	}

	public E findByName(String s)
	{
		for(int i = 0; i < _size; ++i)
		{
			E o = _elements[i];
			if(o != null && s.equalsIgnoreCase(o.getName()))
			{
				return o;
			}
		}
		return null;
	}

	public Deque<E> findAllByName(String s)
	{
		Deque<E> result = new ArrayDeque<E>(1);

		for(int i = 0; i < _size; ++i)
		{
			E o = _elements[i];
			if(o != null && s.equalsIgnoreCase(o.getName()))
			{
				result.add(o);
			}
		}
		return result;
	}

	public List<E> getAll()
	{
		return getAll(new ArrayList<E>(_size));
	}

	public <T extends Collection<E>>T getAll(T list)
	{
		for(int i = 0; i < _size; ++i)
		{
			E o = _elements[i];
			if(o != null)
			{
				list.add(o);
			}
		}
		return list;
	}

	private int indexOf(E o)
	{
		if(o == null)
		{
			return -1;
		}
		for(int i = 0; i < _size; ++i)
		{
			if(o.equals(_elements[i]))
			{
				return i;
			}
		}
		return -1;
	}

	public boolean contains(E o)
	{
		return indexOf(o) > -1;
	}

	@Override
	public String toString()
	{
		return _name;
	}

	@Override
	public Iterator<E> iterator()
	{
		return new Itr();
	}

	class Itr implements Iterator<E>
	{
		private int _cusor;
		private E _next;

		Itr()
		{
			_cusor = 0;
		}

		@Override
		public boolean hasNext()
		{
			do
			{
				if(_cusor >= ObjectArray.this._size)
				{
					return false;
				}
			}
			while((_next = ObjectArray.this._elements[(_cusor++)]) == null);

			return true;
		}

		@Override
		public E next()
		{
			E result = _next;
			_next = null;
			if(result == null)
			{
				throw new NoSuchElementException();
			}
			return result;
		}

		@Override
		public void remove()
		{
			throw new UnsupportedOperationException();
		}
	}
}